#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>


static const char* file_path = NULL;
static char file_name[300];


static void* send_file(void* arg);


int main(int argc, char** argv)
{
    file_path = argv[1]; 

    char* p = NULL;

    p = strrchr(argv[1], '/');

    if(NULL == p)
    {
        strcpy(file_name, argv[1]);
    }
    else
    {
        strcpy(file_name, p + 1);
    }

    //socket 函数：创建一个新套接字
    // 参数意义：
    // 第一个参数：地址家族，通常是 AF_INET
    // 第二个参数：套接字类型，通常有两种：SOCK_STREAM（流套接字，用于 TCP 协议通信） 和 SOCK_DGRAM（数据报式套接字，用于 UDP 协议通信）
    // 第三个参数：通常为 0， 表示使用默认协议。
    // 返回值为新套接字的文件描述符，如果失败则为 -1

    // 第一步：创建一个新的监听套接字
    int sock_listen = socket(AF_INET, SOCK_STREAM, 0);

    if(-1 == sock_listen)
    {
        perror("socket");
        return 1;
    }

    // 第二步：绑定地址

    // 指定地址信息
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;            // 指定地址家族为 Internet 地址家族
    myaddr.sin_addr.s_addr = INADDR_ANY;    // 指定 IP 地址为本机任意地址
    //myaddr.sin_addr.s_addr = inet_addr("192.168.0.56");    // 指定 IP 地址为本机某个确定的 IP 地址
    myaddr.sin_port = htons(8888);          // 指定端口号为 8888
    // htons：将一个短整型数据从主机字节序（通常是小端）转换为网络字节序（通常是大端）
    // inet_addr：将字符串形式的 IP 地址转换为无符号 32 位整数，并且是网络字节序

    // 将上面指定的地址和套接字绑定
    if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
    {
        perror("bind");
        return 1;
    }

    // 第三步：监听
    // listen 函数的第二个参数为监听等待队列的长度
    if(-1 == listen(sock_listen, 5))
    {
        perror("listen");
        return 1;
    }

    while(1)
    {
        // 第四步：接受客户端连接请求

        //accept(sock_listen, NULL, NULL);  // 如果对客户端地址信息不感兴趣

        // 如果想获取当前客户端的地址信息，就使用下面的方式
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);

        // 调用 accept 函数接收一个客户端连接请求（队头请求）
        // 如果成功，返回值为一个套接字描述符，这个套接字是和该客户端一一对应的
        // 这个套接字专门用于和对应的客户端通信，所以通常称它为连接套接字。
        // 如果当前没有任何客户端连接请求到来，accept 函数将会阻塞当前线程，直到成功接收到一个连接请求或出错
        int sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &len);

        if(-1 == sock_conn)
        {
            perror("accept");
            continue;
        }

        printf("\n%s:%hu 已连接...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        pthread_t tid;
        if(pthread_create(&tid, NULL, send_file, (void*)(long)sock_conn))
        {
            perror("pthread_create");
            close(sock_conn);
            continue;
        }
    }

    // 第七步：关闭监听套接字
    close(sock_listen);
    
    return 0;
}


// 负责给对应客户端发送文件的线程
void* send_file(void* arg)
{
    pthread_detach(pthread_self());

    int sock_conn = (int)(long)arg;

    // 第五步：收发数据

    // 发送数据
    char msg[1024];
    int ret;
    unsigned long cnt1 = 0, cnt2 = 0;
    time_t start, end;

    send(sock_conn, file_name, sizeof(file_name), 0);

    int fd = open(file_path, O_RDONLY);

    printf("正在努力发送文件...\n");

    time(&start);

    while((ret = read(fd, msg, sizeof(msg))) > 0)
    {
        cnt1 += ret;

        ret = send(sock_conn, msg, ret, 0);
        //write(sock_conn, msg, sizeof(msg));  // 和上面的写法等效

        if(ret == -1) 
        {
            printf("发送文件失败！\n");
            break;
        }

        cnt2 += ret;
    }

    if(cnt1 != 0 && cnt1 == cnt2)
    {
        time(&end);
        //printf("发送文件成功！（耗时 %lu 秒，平均网速 %lu B/s）\n", end - start, cnt1 / (end -start));
        printf("发送文件成功！（耗时 %lu 秒）\n", end - start);
    }

    // 第六步：断开客户端连接
    close(sock_conn);

    pthread_exit(NULL);
}
